const PENDDING = "PENDDING";
const RESOLVE = "FULLFILLLED";
const REJECT = "REJECTED";

function resolvePromise(p, result, resolve, reject) {
  if (p === result) {
    return reject(new Error('循环引用'));
  }
  let called = false;
  if (result instanceof MyPromise) {
    result.then(v => {
      resolvePromise(p, v, resolve, reject);
    }, r => {
      reject(r);
    });
  } else if (result != null && (typeof result === 'function' || typeof result === 'object')) {
    try {
      const then = result.then;
      if (typeof then === 'function') {
        then.call(result, v => {
          if (called) return;
          called = true;
          resolve(v);
        }, e => {
          if (called) return;
          called = true;
          reject(e);
        });
      }
    } catch (error) {
      reject(error);
    }

  } else {
    resolve(result);
  }
}

function resolveAll(len, resolve) {
  let count = 0;
  let res = [];
  return function (i, val) {
    res[i] = val;
    if (++count === len) {
      resolve(res);
    }
  }
}

class MyPromise {
  constructor(exector) {
    this.state = PENDDING;
    this.value = void 0;
    this.reason = void 0;
    this.resolveCallBacks = [];
    this.rejectCallBacks = [];

    const resolve = val => {
      if (this.state === PENDDING) {
        this.state = RESOLVE;
        this.value = val;
        this.resolveCallBacks.forEach(fn => fn(val));
      }
    }
    const reject = reason => {
      if (this.state === PENDDING) {
        this.state = REJECT;
        this.reason = reason;
        this.rejectCallBacks.forEach(fn => fn(reason));
      }
    }
    /* 执行 */
    try {
      exector(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }

  /* then 方法 */
  then(onFullFill, onReject) {
    onFullFill = typeof onFullFill === 'function' ? onFullFill : val => val;
    onReject = typeof onReject === 'function' ? onReject : val => val;

    const that = this;
    let p;

    if (this.state === PENDDING) {
      p = new MyPromise((resolve, reject) => {
        that.resolveCallBacks.push((val) => {
          try {
            let result = onFullFill(val);
            resolvePromise(p, result, resolve, reject);
          } catch (error) {
            reject(error);
          }
        });
        that.rejectCallBacks.push((reason) => {
          try {
            let result = onReject(reason);
            resolvePromise(p, result, resolve, reject);
          } catch (error) {
            reject(error);
          }
        });
      });

    } else if (this.state === RESOLVE) {
      p = new MyPromise((resolve, reject) => {
        let result = onFullFill(that.value);
        resolvePromise(p, result, resolve, reject);
      });
    } else if (this.state === REJECT) {
      p = new MyPromise((resolve, reject) => {
        let result = onReject(that.reason);
        resolvePromise(p, result, resolve, reject);
      });
    }
    return p;
  }

  /* catch */
  catch (onRejct) {
    return this.then(null, onRejct);
  }
  /* finally */
  finally(callback) {
    let P = this.constructor;
    return this.then(val => {
      P.resolve(callback()).then(() => val);
    }, reason => {
      P.resolve(callback()).then(() => {
        throw reason
      });
    });
  }
  /* all*/
  static all(pArr) {
    return new MyPromise((slv, rej) => {
      let checkDone = resolveAll(pArr.length, slv);
      pArr.forEach((p, i) => {
        p.then(v => {
          checkDone(i, v);
        }, rej);
      });
    });
  }

  /*race*/
  static race(pArr) {
    return new MyPromise((rsl, rjt) => {
      pArr.forEach(p => {
        p.then(rsl, rjt);
      });
    });
  }

  /* resolve */
  static resolve(val) {
    return new MyPromise((rsl, rjt) => {
      rsl(val);
    });
  }

  /* reject */
  static reject(val) {
    return new MyPromise((rsl, rjt) => {
      rjt(val);
    });
  }
}



const p = new MyPromise((resolve, reject) => {
  // resolve(1);
  reject('errrr');
});

p.then(v => {
  console.log(v);
  return 100;
}).then(v => {
  console.log(v);
  return 1;
}).catch(e => {
  console.log(e);
});

const p1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('p1');
  }, 100);
})
const p2 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('p2');
  }, 50);
})

MyPromise.all([p1, p2]).then(res => console.log('all:', res), e => console.log(`all e:${e}`));
MyPromise.race([p1, p2]).then(res => console.log(`race:${res}`), e => console.log(`race e:${e}`));


MyPromise.resolve(1).then(v => console.log(`resolve:${v}`));
MyPromise.reject('reject').catch(v => console.log(`reject:${v}`));
